package game.manager;

import game.Constants;
import game.Event;
import game.Event.EventType;
import game.Person;
import game.Person.Gender;
import game.Person.Orientation;
import game.util.TraitUtil.Trait;
import game.util.Util;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Observable;
import java.util.Vector;

public class RelationshipManager implements Manager {
	private Collection<Relationship> relationships;

	@Override
	public void gameUpdate() {

	}

	private static RelationshipManager manager;

	public static RelationshipManager getManager() {
		if (manager == null)
			manager = new RelationshipManager();
		return manager;
	}

	public List<Relationship> getRelationships(Person person) {
		Vector<Relationship> personRelationships = new Vector<Relationship>();
		for (Relationship relationship : this.relationships) {
			if (relationship.getPeople().contains(person))
				personRelationships.add(relationship);
		}
		return personRelationships;
	}

	private RelationshipManager() {
		this.initialize();
	}

	@Override
	public void initialize() {
		this.relationships = new Vector<Relationship>();
	}

	public void addRelationship(Person a, Person b) {
		final Date now = new Date();
		final int initialStrength = getInitialAttraction(a, b);
		final Relationship relationship = new Relationship(a, b, now,
				initialStrength);
		if (!this.relationships.contains(relationship)) {
			this.relationships.add(relationship);
			EventManager.getManager().sendEvent(
					new Event(EventType.RELATIONSHIP_STARTED));
		} else
			throw new IllegalStateException(
					"Attempted to re-add a relationship");
	}

	public Relationship getRelationship(Person a, Person b) {
		for (Relationship relationship : this.relationships) {
			if (relationship.isBetween(a, b))
				return relationship;
		}
		throw new IllegalArgumentException("Relationship not found");
	}

	@Override
	public void update(Observable o, Object arg) {
		final Event event = (Event) arg;

		switch (event.getType()) {
		case RESET:
			this.initialize();
			break;
		}
	}

	/**
	 * Calculates a's reception of b's traits. Assumes order in Traits. Returns
	 * a value between 0 and 100.
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public int getTraitsCompatibility(Person a, Person b) {
		int compatibility = 0;

		List<Trait> aTraits = a.getTraits();
		List<Trait> bTraits = b.getTraits();

		// for each trait
		for (int index = 0; index < Trait.NUM_TRAITS; ++index) {
			final Trait aTrait = aTraits.get(index);
			final Trait bTrait = bTraits.get(index);
			// start with the highest possible compatibility
			int traitCompatibility = Constants.TRAIT_COMPATIBILITY_WEIGHT;

			int rawDifference = aTrait.getValue() - bTrait.getValue();
			int boundedDifference;
			if (rawDifference >= 0) {
				boundedDifference = Util.GetBoundedValue(
						Constants.TRAIT_COMPATIBILITY_MAX,
						Constants.TRAIT_COMPATIBILITY_MIN, rawDifference);

			} else {
				boundedDifference = Util.GetBoundedValue(
						Constants.TRAIT_COMPATIBILITY_MIN,
						-Constants.TRAIT_COMPATIBILITY_MAX, rawDifference);
			}

			// deduct every point under or over the threshold
			if (boundedDifference < Constants.TRAIT_COMPATIBILITY_MIN)
				traitCompatibility += boundedDifference;
			else if (boundedDifference > Constants.TRAIT_COMPATIBILITY_MAX)
				traitCompatibility -= boundedDifference;

			compatibility += traitCompatibility;
		}

		return compatibility;
	}

	/**
	 * Calculates a's reception of b's looks. Returns a number between
	 * ATTRACTION_LOOKS_MAX and ATTRACTION_LOOKS_MIN;
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public int getLooksCompatibility(Person a, Person b) {
		int compatibility = Constants.ATTRACTION_LOOKS_MAX;
		
		final int aTotal = a.getModifiedLooks();
		final int bTotal = b.getModifiedLooks();

		// lower bounded by Constants.ATTRACTION_LOOKS_MIN
		int difference = Math.max(Constants.ATTRACTION_LOOKS_MIN, aTotal
				- bTotal);

		if (difference > 0)
			compatibility -= difference;

		return compatibility;
	}

	private boolean isOrientationCompatible(Person a, Person b) {
		boolean interested = true;
		final Orientation orientation = a.getOrientation();
		final Gender aGender = a.getGender();
		final Gender bGender = b.getGender();

		switch (orientation) {
		case STRAIGHT:
			if (aGender == bGender)
				interested = false;
			break;
		case GAY:
			if (aGender != bGender)
				interested = false;
			break;
		}
		return interested;
	}

	/**
	 * Calculates a's attraction to b. (May differ than b's attraction to a)
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	private int getInitialAttraction(Person a, Person b) {
		int attraction = 0;
		int looks = 0;
		int traits = 0;

		boolean orientationCompatible = this.isOrientationCompatible(a, b);
		if (orientationCompatible) {
			looks = this.getLooksCompatibility(a, b);
		}

		traits = this.getTraitsCompatibility(a, b);

		attraction = (traits * Constants.ATTRACTION_TRAIT_WEIGHT + looks
				* Constants.ATTRACTION_LOOKS_WEIGHT) / 100;

		return attraction;
	}

	public class Relationship {
		private Vector<Person> people;
		private int strength;
		private Date start;

		public Relationship(Person a, Person b, Date start, int startStrength) {
			this.people = new Vector<Person>(2);
			this.people.add(a);
			this.people.add(b);
			this.start = start;
			this.strength = startStrength;
		}

		public boolean isBetween(Person cPerson, Person dPerson) {
			final Collection<Person> bPeople = new Vector<Person>(2);
			bPeople.add(cPerson);
			bPeople.add(dPerson);
			return this.people.containsAll(bPeople);
		}

		@Override
		public boolean equals(Object obj) {
			if (obj instanceof Relationship) {
				Collection<Person> otherPeople = ((Relationship) obj)
						.getPeople();
				return otherPeople.containsAll(people);
			}
			return false;
		}

		public int getStrength() {
			return strength;
		}

		public Collection<Person> getPeople() {
			return new Vector<Person>(people);
		}

		@Override
		public String toString() {
			return "Relationship[" + this.people + ", " + this.strength + "]";
		}

		/***
		 * Gets the length of the relationship in minutes
		 */
		public int getLength() {
			return (int) ((new Date().getTime() - start.getTime()) / Constants.ONE_MINUTE);
		}
	}
}
